using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class ProgramROMRAM : Component
    {
        public NodeVector O_IR;
        public NodeVector O_CONST;
        public NodeVector I_PC;
        public NodeVector I_WRITE_DATA_CONST;
        public NodeVector I_WRITE_DATA_IR;
        public NodeVector I_ADDRESS;
        public NodeVector I_WRITE;
        ProgramTable oROMProgram;
        ProgramTable oRAMProgram;

        //NodeVector nvReset;
        ClockComponent oClockComponet;

        int iPCWidth = 0;
        int iRAMStartAddress = 0;
        int iRAMDepth = 0;
        int iConstWidth=0;

        string sFileText="";
        Dictionary<int, int> dicAddressToLine = new Dictionary<int, int>();
        List<int> lBreakpoints = new List<int>();

        FPGABlock oFPGARAM;

        public ProgramROMRAM(Component poParentComponent, string psName, ProgramTable poROMProgramTable, ProgramTable poRAMProgramTable, int piRAMStartAddress, int piRAMDepth, int piConstWidth, ClockComponent poClock)
            : base (poParentComponent,psName)
        {
            oROMProgram = poROMProgramTable;
            O_IR = this.CreateNodeVector("IR_O", oROMProgram.IRWidth);
            O_CONST = this.CreateNodeVector("CONST_O", piConstWidth);
            iPCWidth = Conversion.MinBitWidth(piRAMStartAddress + piRAMDepth - 1);
            I_PC = this.CreateNodeVector("PC_I",iPCWidth);
            I_WRITE_DATA_CONST = this.CreateNodeVector("WRITE_DATA_CONST_I",piConstWidth);
            I_WRITE_DATA_IR = this.CreateNodeVector("WRITE_DATA_IR_I",oROMProgram.IRWidth);
            I_ADDRESS = this.CreateNodeVector("ADDRESS_I", iPCWidth);
            iRAMStartAddress = piRAMStartAddress;
            iRAMDepth = piRAMDepth;
            iConstWidth = piConstWidth;
            I_WRITE = this.CreateNodeVector("WRITE_I", 1);
            oClockComponet = poClock;
            oRAMProgram = poRAMProgramTable;
        }

        NodeVector nvSelect;
        NodeVector nvRAMAddress;
        NodeVector nvRAMIR;
        NodeVector nvRAMConst;

        public override void GenerateStructure()
        {
            //SignalSource oWrite = RapidHardware.SystemInterface.AddSignalSource(Name + "_WRITE_I",I_WRITE,0);
            //SignalSource oWriteDataConst = RapidHardware.SystemInterface.AddSignalSource(Name + "_WRITE_DATA_CONST_I",I_WRITE_DATA_CONST,0);
            //SignalSource oWriteDataIR = RapidHardware.SystemInterface.AddSignalSource(Name + "_WRITE_DATA_IR_I",I_WRITE_DATA_IR,0);
            //SignalSource oAddress = RapidHardware.SystemInterface.AddSignalSource(Name + "_ADDRESS_I",I_ADDRESS,0);

            ProgramROM oProgramROM = new ProgramROM(this, "program_rom", oROMProgram);
            RAM oRAM = new RAM(this, "program_ram", oClockComponet, oROMProgram.IRWidth + iConstWidth, iRAMDepth,true);
            Mux oIRMux = new Mux(this, "ir_mux", oROMProgram.IRWidth, 2);
            Mux oConstMux = new Mux(this, "const_mux", iConstWidth, 2);
            CompareGreaterEqual oCompare = new CompareGreaterEqual(this, "ram_sel", iPCWidth, iPCWidth);

            oRAM.I_READ.Join(1);

            oCompare.InputNodesA.Join(I_PC);
            oCompare.InputNodesB.Join(iRAMStartAddress);
            oIRMux.SelectNodes.Join(oCompare.Output);
            nvSelect = oCompare.Output;
            oConstMux.SelectNodes.Join(oCompare.Output);

            oIRMux.InputNodes[0].Join(oProgramROM.O_IR);
            NodeVector nvROMConst = this.CreateNodeVector("rom_const",iConstWidth);
            if (nvROMConst.Width <= oProgramROM.O_CONST.Width)
            {
                nvROMConst.Join(oProgramROM.O_CONST);
            }
            else
            {
                nvROMConst[0,oProgramROM.O_CONST.Width -1].Join(oProgramROM.O_CONST);
                nvROMConst[oProgramROM.O_CONST.Width,nvROMConst.Width -1].Join(0);
            }

            oConstMux.InputNodes[0].Join(nvROMConst);

            oIRMux.InputNodes[1].Join(oRAM.O_DATA[iConstWidth, iConstWidth + oROMProgram.IRWidth - 1]);
            oConstMux.InputNodes[1].Join(oRAM.O_DATA[0, iConstWidth - 1]);

            oProgramROM.I_PC.Join(I_PC);

            oRAM.I_WRITE.Join(I_WRITE);
            oRAM.I_DATA[0,iConstWidth-1].Join(I_WRITE_DATA_CONST);
            oRAM.I_DATA[iConstWidth , iConstWidth + oROMProgram.IRWidth - 1].Join(I_WRITE_DATA_IR);

            Mux oAddressMux = new Mux(this,"addr_mux",iPCWidth,2);
            oAddressMux.SelectNodes.Join(I_WRITE);
            oAddressMux.InputNodes[0].Join(I_PC);
            oAddressMux.InputNodes[1].Join(I_ADDRESS);

            ALUComponent oALU = new ALUComponent(this, "sub_offset", ALUType.Subtract, iPCWidth);
            oALU.I_A.Join(oAddressMux.OutputNodes);
            oALU.I_B.Join(iRAMStartAddress); 
            oRAM.I_ADDRESS.Join(oALU.O_A);
            oRAM.I_ENABLE.Join(1);
            nvRAMAddress = oALU.O_A;

            nvRAMIR = oIRMux.InputNodes[1];
            nvRAMConst = oConstMux.InputNodes[1];

            O_IR.Join(oIRMux.OutputNodes);
            O_CONST.Join(oConstMux.OutputNodes);

            UpdateProgramTrace(oROMProgram);
            UpdateProgramTrace(oRAMProgram);
            RegisterRAM(oRAMProgram);
        }

        public void AddSignalViews()
        {
            ParentComponent.RapidHardware.SystemInterface.AddSignalView(Name + "_RAM_SEL", nvSelect);
            ParentComponent.RapidHardware.SystemInterface.AddSignalView(Name + "_RAM_ADDR", nvRAMAddress);
            ParentComponent.RapidHardware.SystemInterface.AddSignalView(Name + "_RAM_IR", nvRAMIR);
            ParentComponent.RapidHardware.SystemInterface.AddSignalView(Name + "_RAM_CONST", nvRAMConst);
        }

        public void SignalDisplay(HardwareScript poScript, int piClockTime)
        {
            poScript.SignalView(piClockTime, Name + "_RAM_SEL");
            poScript.SignalView(piClockTime, Name + "_RAM_ADDR",SignalFormat.Unsigned);
            poScript.SignalView(piClockTime, Name + "_RAM_IR", SignalFormat.Unsigned);
            poScript.SignalView(piClockTime, Name + "_RAM_CONST", SignalFormat.Unsigned);
        }

        private void UpdateProgramTrace(ProgramTable oProgram)
        {
            int iLines = Utility.Count(sFileText,"\n");
            foreach (int iAddress in oProgram.AddressToLine.Keys)
            {
                int iLine = oProgram.AddressToLine[iAddress] + iLines;
                dicAddressToLine.Add(iAddress, iLine);
            }
            sFileText += oProgram.FileText;
            foreach (int iBreakpoint in oProgram.Breakpoints)
            {
                lBreakpoints.Add(iBreakpoint);
            }
        }

        private void RegisterRAM(ProgramTable poRAMProgram)
        {
            oFPGARAM = new FPGABlock();
            foreach (int iAddress in poRAMProgram.ProgramRAMIR.Keys)
            {
                int iIR = poRAMProgram.ProgramRAMIR[iAddress];
                int iConst = poRAMProgram.ProgramRAMConstant[iAddress];
                oFPGARAM.LoadA(iIR);
                oFPGARAM.LoadB(iConst);
                oFPGARAM.WriteProgRAM(iAddress);
            }
        }

        public bool WriteRAMToFPGA()
        {
            ParentComponent.RapidHardware.FPGAInterface.Reset();
            ParentComponent.RapidHardware.FPGAInterface.StartClock();
            IOInterface.PostFPGA(oFPGARAM);
            //return true;
            ParentComponent.RapidHardware.FPGAInterface.StopClock();
            FPGABlock oStartBlock = new FPGABlock();
            oStartBlock.AddEntry(0, 61, 0, true);
            IOInterface.PostFPGA(oStartBlock);
            return true;
        }

        public void LoadRAMProgram()
        {
            WriteRAMToFPGA();
            /*RapidHardware.FPGAInterface.StopClock();
            SignalSource oWrite = RapidHardware.SystemInterface.GetSignalSource(Name + "_WRITE_I");
            SignalSource oWriteDataConst = RapidHardware.SystemInterface.GetSignalSource(Name + "_WRITE_DATA_CONST_I");
            SignalSource oWriteDataIR = RapidHardware.SystemInterface.GetSignalSource(Name + "_WRITE_DATA_IR_I");
            SignalSource oAddress = RapidHardware.SystemInterface.GetSignalSource(Name + "_ADDRESS_I");

            SignalView oConstTest = RapidHardware.SystemInterface.GetSignalView(Name + "_RAM_CONST");
            SignalView oIRTest = RapidHardware.SystemInterface.GetSignalView(Name + "_RAM_IR");

            foreach (int iAddress in oRAMProgram.ProgramRAMConstant.Keys)
            {
                int iIR = oRAMProgram.ProgramRAMIR[iAddress];
                int iConst = oRAMProgram.ProgramRAMConstant[iAddress];
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);
                oWriteDataConst.ValueAsInt = iConst;
                oWriteDataIR.ValueAsInt = iIR;
                oAddress.ValueAsInt = iAddress;
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);
                oWrite.ValueAsInt = 1;
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);
                /*if (oConstTest.ValueAsInt != iConst)
                    return;
                if (oIRTest.ValueAsInt != iIR)
                    return;
                    
                oWrite.ValueAsInt = 0;
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);

            }

         
            foreach (int iAddress in oRAMProgram.ProgramRAMConstant.Keys)
            {
                int iIR = oRAMProgram.ProgramRAMIR[iAddress];
                int iConst = oRAMProgram.ProgramRAMConstant[iAddress];
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);
                oWriteDataConst.ValueAsInt = 0;
                oWriteDataIR.ValueAsInt = 0;
                oAddress.ValueAsInt = iAddress;
                RapidHardware.FPGAInterface.Go(100);
                System.Threading.Thread.Sleep(100);
                if (oConstTest.ValueAsInt != iConst)
                    return;
                if (oIRTest.ValueAsInt != iIR)
                    return;
                                
         }

            RapidHardware.FPGAInterface.Reset();
             */
        }

        public Dictionary<int, int> AddressToLine
        {
            get { return dicAddressToLine; }
        }

        public string FileText
        {
            get { return sFileText; }
        }

        public List<int> Breakpoints
        {
            get { return lBreakpoints; }
        }
    }
}
